package pcloud.client.remoteservice;

import it.sauronsoftware.ftp4j.FTPAbortedException;
import it.sauronsoftware.ftp4j.FTPClient;
import it.sauronsoftware.ftp4j.FTPDataTransferException;
import it.sauronsoftware.ftp4j.FTPException;
import it.sauronsoftware.ftp4j.FTPFile;
import it.sauronsoftware.ftp4j.FTPIllegalReplyException;
import it.sauronsoftware.ftp4j.FTPListParseException;
import it.sauronsoftware.ftp4j.FTPReply;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import pcloud.client.util.ClientIdentifier;
import pcloud.client.util.FileResourceInfo;
import org.apache.log4j.Logger;

public class FTP4JService implements FileService
{
	private static Logger logger = Logger.getLogger(FTP4JService.class);

	private FTPClient ftpClient = new FTPClient();

	/**
	 * 连接到FTP服务器
	 * 
	 * @param hostname
	 *            主机名
	 * @param port
	 *            端口
	 * @param username
	 *            用户名
	 * @param password
	 *            密码
	 * @return 是否连接成功
	 * @throws IOException
	 */
	private boolean connect(String hostname, int port, String username,
			String password)
	{
		try {
			ftpClient.connect(hostname, port);
			ftpClient.setType(FTPClient.TYPE_BINARY);
			ftpClient.setCharset("UTF-8");
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("与FTP服务器建立连接失败!");
			return false;
		}
		try {
			ftpClient.login(username, password);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("登录FTP服务器失败！");
			return false;
		}
		try {
			FTPReply reply = ftpClient.sendCustomCommand("CLIENT_ID "+ClientIdentifier.getID());
			reply.getMessages();
		} catch (IllegalStateException e) {
			logger.error("发送客户端ID失败！"+e.getMessage());
			return false;
		} catch (FTPIllegalReplyException e) {
			logger.error("发送客户端ID失败！"+e.getMessage());
			return false;
		} catch (IOException e) {
			logger.error("发送客户端ID失败！"+e.getMessage());
			return false;
		}
		return true;
	}

	/**
	 * 从FTP服务器上下载文件,支持断点续传，上传百分比汇报
	 * 
	 * @param remote
	 *            远程文件路径
	 * @param local
	 *            本地文件路径
	 * @return 上传的状态
	 * @throws IOException
	 */
	private boolean download(String remote, String local) throws IOException
	{
		// 检查远程文件是否存在
		// String encodeRemote = new String(
		// remote.getBytes("UTF-8"), "iso-8859-1");
		// long length=0;
		try {
			@SuppressWarnings("unused")
			long length = ftpClient.fileSize(remote);
		} catch (IllegalStateException e) {
			logger.error("获取文件大小失败:"+e.getMessage());
			throw new RuntimeException(e);
		} catch (FTPIllegalReplyException e) {
			logger.error("获取文件大小失败:"+e.getMessage());
			throw new RuntimeException(e);
		} catch (FTPException e) {
			logger.error("获取文件大小失败:"+e.getMessage());
			throw new RuntimeException(e);
		}

		File f = new File(local);
		try {
			ftpClient.download(remote, f);
		} catch (IllegalStateException e) {
			logger.error("下载文件失败:"+e.getMessage());
			throw new RuntimeException(e);
		} catch (FTPIllegalReplyException e) {
			logger.error("下载文件失败:"+e.getMessage());
			throw new RuntimeException(e);
		} catch (FTPException e) {
			logger.error("下载文件失败:"+e.getMessage());
			throw new RuntimeException(e);
		} catch (FTPDataTransferException e) {
			logger.error("下载文件失败:"+e.getMessage());
			throw new RuntimeException(e);
		} catch (FTPAbortedException e) {
			throw new RuntimeException(e);
		}
		return true;
	}

	/**
	 * 断开与远程服务器的连接
	 * 
	 * @throws IOException
	 */
	private void disconnect() throws IOException
	{
		if (ftpClient.isConnected()) {
			try {
				ftpClient.disconnect(true);
			} catch (IllegalStateException e) {
				logger.error("断开连接失败:"+e.getMessage());
			} catch (FTPIllegalReplyException e) {
				logger.error("断开连接失败:"+e.getMessage());
			} catch (FTPException e) {
				logger.error("断开连接失败:"+e.getMessage());
			}
		}
	}

	/**
	 * 递归创建远程服务器目录
	 * 
	 * @param path
	 *            远程服务器文件绝对路径
	 * @param ftpClient
	 *            FTPClient对象
	 * @return 目录创建是否成功
	 * @throws IOException
	 */
	private boolean createDirectory(String path) throws IOException
	{
		try {
			ftpClient.createDirectory(path);
			return true;
		} catch (IllegalStateException e2) {
			e2.printStackTrace();
			return false;
		} catch (FTPIllegalReplyException e2) {
			e2.printStackTrace();
			return false;
		} catch (FTPException e) {
			//如果文件夹已存在, 也算成功了, 因为前后端已经一致
			if (e.getCode()==550) {
				logger.info("FTP4J Service, createDirectory: '"+path+"', file already exists!");
				return true;
			}
			return false;
		}
	}

	private UploadStatus upload(String remotePath, File localFile)
			throws IOException
	{
		return upload(remotePath, localFile, 0);
	}

	/**
	 * 上传文件到服务器,新上传和断点续传
	 * 
	 * @param remoteFile
	 *            远程文件名，在上传之前已经将服务器工作目录做了改变
	 * @param localFile
	 *            本地文件File句柄，绝对路径
	 * @param processStep
	 *            需要显示的处理进度步进值
	 * @param ftpClient
	 *            FTPClient引用
	 * @return
	 * @throws IOException
	 */
	private UploadStatus upload(String remotePath, File localFile,
			long remoteSize) throws IOException
	{
		// 如果不需要断点续传, 那么新建一个文件
		if (remoteSize <= 0) {
			try {
				ftpClient.changeDirectory(remotePath);
				ftpClient.upload(localFile);
			} catch (IllegalStateException e) {
				logger.error("上传文件失败:"+e.getMessage());
				return UploadStatus.Upload_New_File_Failed;
			} catch (FTPIllegalReplyException e) {
				logger.error("上传文件失败:"+e.getMessage());
				return UploadStatus.Upload_New_File_Failed;
			} catch (FTPException e) {
				logger.error("上传文件失败:"+e.getMessage());
				return UploadStatus.Upload_New_File_Failed;
			} catch (FTPDataTransferException e) {
				logger.error("上传文件失败:"+e.getMessage());
				return UploadStatus.Upload_New_File_Failed;
			} catch (FTPAbortedException e) {
				logger.error("上传文件失败:"+e.getMessage());
				return UploadStatus.Upload_New_File_Failed;
			} finally {

			}
		}
		return UploadStatus.Upload_New_File_Success;

		// // 如果文件存在则覆盖或续传
		// // 显示进度的上传
		// UploadStatus status;
		// long step = localFile.length() / 100;
		// long process = 0;
		// long localreadbytes = 0L;
		// RandomAccessFile raf = new RandomAccessFile(localFile, "r");
		// // 断点续传
		// if (remoteSize > 0) {
		// ftpClient.setRestartOffset(remoteSize);
		// process = remoteSize / step;
		// raf.seek(remoteSize);
		// localreadbytes = remoteSize;
		// }
		// byte[] bytes = new byte[1024];
		// int c;
		// OutputStream out = ftpClient.appendFileStream(remote);
		// while ((c = raf.read(bytes)) != -1) {
		// out.write(bytes, 0, c);
		// localreadbytes += c;
		// if (localreadbytes / step != process) {
		// process = localreadbytes / step;
		// System.out.println("上传进度:" + process);
		// 
		//
		// }
		// }
		// out.flush();
		// raf.close();
		// out.close();
		// boolean result = ftpClient.completePendingCommand();
		// if (remoteSize > 0) {
		// status = result ? UploadStatus.Upload_From_Break_Success
		// : UploadStatus.Upload_From_Break_Failed;
		// } else {
		// status = result ? UploadStatus.Upload_New_File_Success
		// : UploadStatus.Upload_New_File_Failed;
		// }
		// return status;
	}

	/**
	 * 获取指定目录下的文件与目录信息集合
	 * 
	 * @param path
	 *            指定的当前目录
	 * @return 返回的文件集合
	 */
	private FTPFile[] listDirectory(String path)
	{
		FTPFile[] files = null;

		try {
			if (path == null)
				files = ftpClient.list();
			else
				files = ftpClient.list(path);
		} catch (IOException e) {
			logger.error("获取目录列表失败:"+e.getMessage());
		} catch (IllegalStateException e) {
			logger.error("获取目录列表失败:"+e.getMessage());
		} catch (FTPIllegalReplyException e) {
			logger.error("获取目录列表失败:"+e.getMessage());
		} catch (FTPException e) {
			logger.error("获取目录列表失败:"+e.getMessage());
		} catch (FTPDataTransferException e) {
			logger.error("获取目录列表失败:"+e.getMessage());
		} catch (FTPAbortedException e) {
			logger.error("获取目录列表失败:"+e.getMessage());
		} catch (FTPListParseException e) {
			logger.error("获取目录列表失败:"+e.getMessage());
		}
		if (files==null) {
			files = new FTPFile[0];
		}
		logger.info("获取目录列表成功, 目录路径:'"+path+"'");
		return files;
	}

	private FTP4JService()
	{
	};

	public static FileService createInstance(String serviceUrl,
			String username, String password)
	{
		FTP4JService service = new FTP4JService();
		
		service.validateRootUrl(serviceUrl);
		logger.info("正在创建FTP服务实例...验证路径成功!");

		service.ftpClient = new FTPClient();
		boolean successfully = service.connect(serviceUrl, 21, username, password);
		logger.info("正在创建FTP服务实例,..连接服务器成功!");

		return successfully ? service : null;
	}

	@Override
	public void release()
	{
		try {
			this.disconnect();
			logger.info("断开FTP连接成功!");
		} catch (IOException e) {
			logger.error("断开FTP连接失败! 异常信息:"+e.getMessage());
		}
	}

	private String validateRootUrl(String url)
	{
		if (url.charAt(url.length() - 1) == '/') {
			return url.substring(0, url.length() - 1);
		}
		return url;
	}

	@Override
	public boolean createFolder(FileResourceInfo fileInfo) throws IOException
	{
		if (!ftpClient.isConnected()) {
			logger.error("FTP连接已断开, 无法创建目录! 目录路径:'"+fileInfo.getFullPath());
			return false;
		}
		if (!fileInfo.isRemote() || !fileInfo.isFolder()) {
			logger.error("文件资源描述不符合远程目录要求, 无法创建目录! 目录路径:'"+fileInfo.getFullPath());
			return false;
		}
		return createDirectory(fileInfo.getFullPath());
	}

	@Override
	public boolean delete(FileResourceInfo fileInfo) throws IOException
	{
		if (!ftpClient.isConnected()) {
			logger.error("FTP连接已断开, 无法删除文件! 文件路径:'"+fileInfo.getFullPath());
			return false;
		}

		if (fileInfo.isFolder()) {
			logger.info("正在删除远程目录...目录路径:"+fileInfo.getFullPath());
			return deleteFolder(fileInfo.getFullPath());
		} else {
			logger.info("正在删除远程文件...文件路径:"+fileInfo.getFullPath());
			return deleteFile(fileInfo.getFullPath());
		}
	}

	private boolean deleteFile(String path) throws IOException
	{
		try {
			ftpClient.deleteFile(path);
			logger.info("删除远程文件成功! 文件路径:"+path);
			return true;
		} catch (IllegalStateException e) {
			logger.error("删除远程文件失败! 文件路径:'"+path+"', 异常信息:"+e.getMessage());
		} catch (FTPIllegalReplyException e) {
			logger.error("删除远程文件失败! 文件路径:'"+path+"', 异常信息:"+e.getMessage());
		} catch (FTPException e) {
			logger.error("删除远程文件失败! 文件路径:'"+path+"', 异常信息:"+e.getMessage());
		} catch (Exception e) {
			logger.error("删除远程文件失败! 文件路径:'"+path+"', 异常信息:"+e.getMessage());
		}
		return false;
	}

	private boolean deleteFolder(String path) throws IOException
	{
		try {
			ftpClient.deleteDirectory(path);
			logger.info("删除远程目录成功! 目录路径:"+path);
			return true;
		} catch (IllegalStateException e) {
			logger.error("删除远程目录失败! 目录路径:'"+path+"', 异常信息:"+e.getMessage());
		} catch (FTPIllegalReplyException e) {
			logger.error("删除远程目录失败! 目录路径:'"+path+"', 异常信息:"+e.getMessage());
		} catch (FTPException e) {
			logger.error("删除远程目录失败! 目录路径:'"+path+"', 异常信息:"+e.getMessage());
		} catch (Exception e) {
			logger.error("删除远程目录失败! 目录路径:'"+path+"', 异常信息:"+e.getMessage());
		}
		return false;
	}

	@Override
	public boolean uploadFile(FileResourceInfo fileInfo) throws IOException
	{
		if (!ftpClient.isConnected()) {
			logger.error("FTP连接已断开, 无法上传文件! 文件路径:'"+fileInfo.getFullPath());
			return false;
		}

		File localFile = new File(fileInfo.getFullPath());
		String remotePath = FileResourceInfo.getPathWithoutFileName(fileInfo
				.toRemote().getFullPath());
		
		logger.info("正在上传文件...远程路径:'"+remotePath+"', 本地路径:"+fileInfo.getFullPath()+"'");
		upload(remotePath, localFile);
		logger.info("上传文件成功! 远程路径:'"+remotePath+"', 本地路径:"+fileInfo.getFullPath()+"'");
		return true;
	}

	@Override
	public boolean uploadFile(String remotePath, String localPath)
	{
		File localFile = new File(localPath);
		String remotePathWithoutFileName = FileResourceInfo.getPathWithoutFileName(remotePath);
		try {
			upload(remotePathWithoutFileName, localFile);
		} catch (IOException e) {
			logger.error("upload failed! [" +remotePath+"]\r\n"+e.getMessage());
			return false;
		}
		return true;
	}

	@Override
	public boolean downloadFile(FileResourceInfo fileInfo) throws IOException
	{
		if (!fileInfo.isRemote()) {
			fileInfo = fileInfo.toRemote();
		}
		String remoteFullPath = fileInfo.getFullPath();

		FileResourceInfo localFileInfo = fileInfo.toLocal();
		if (localFileInfo==null) {
			logger.error("download failed! [" +remoteFullPath+"]\r\ncan not find local path!");
			return false;
		}
		
		String localFullPath = localFileInfo.getFullPath();
		
		return download(remoteFullPath, localFullPath);
	}


	@Override
	public List<FileResourceInfo> list(String path) throws IOException
	{
		FTPFile[] ftpFiles = listDirectory(path);
		List<FileResourceInfo> fileInfoList = new LinkedList<FileResourceInfo>();
		for (FTPFile ftpFile : ftpFiles) {
			FileResourceInfo fileInfo = createFileResourceInfo(path, ftpFile);
			fileInfoList.add(fileInfo);
		}

		return fileInfoList;
	}

	@Override
	public FileResourceInfo getResource(String path) throws IOException
	{
		FileResourceInfo fileInfo = new FileResourceInfo();
		fileInfo.setFullPath(path);
		fileInfo.setRemote(true);
		try {
			fileInfo.setLength(ftpClient.fileSize(path));
		} catch (IllegalStateException e) {
			logger.error("get resource info failed! [" +path+"]\r\n"+e.getMessage());
		} catch (FTPIllegalReplyException e) {
			logger.error("get resource info failed! [" +path+"]\r\n"+e.getMessage());
		} catch (FTPException e) {
			logger.error("get resource info failed! [" +path+"]\r\n"+e.getMessage());
		}

		try {
			fileInfo.setLastModified(ftpClient.modifiedDate(path).getTime());
		} catch (IllegalStateException e1) {
			logger.error("get resource info failed! [" +path+"]\r\n"+e1.getMessage());
		} catch (FTPIllegalReplyException e1) {
			logger.error("get resource info failed! [" +path+"]\r\n"+e1.getMessage());
		} catch (FTPException e1) {
			logger.error("get resource info failed! [" +path+"]\r\n"+e1.getMessage());
		}
		// FTP中没有直接判断路径是目录还是文件的命令, 但目录的大小一定为0
		// 采用切换当前目录的方法尝试一下, 如果成功, 认为它是一个目录
		if (fileInfo.getLength() == 0) {
			try {
				ftpClient.changeDirectory(path);
				fileInfo.setFolder(true);
			} catch (Exception e) {
				logger.error("get resource info failed! [" +path+"]\r\n"+e.getMessage());			e.printStackTrace();
			}
		}
		return fileInfo;
	}

	private FileResourceInfo createFileResourceInfo(String path, FTPFile ftpFile)
	{
		FileResourceInfo fileInfo = new FileResourceInfo();
		fileInfo.setLength(ftpFile.getSize());
		fileInfo.setLastModified(ftpFile.getModifiedDate().getTime());
		fileInfo.setFilename(ftpFile.getName());
		fileInfo.setFullPath(path 
							+ (path.endsWith("/") ? "" : "/")
							+ ftpFile.getName());

		if (ftpFile.getType() == FTPFile.TYPE_DIRECTORY) {
			fileInfo.setFolder(true);
		}
		fileInfo.setRemote(true);
		return fileInfo;
	}

	public boolean isConnected()
	{
		return ftpClient.isConnected();
	}


	@Override
	public String toString()
	{
		return "isConnected: " + ftpClient.isConnected() + "\r\n" + "Host: "
				+ ftpClient.getHost() + "\r\n" + "Port: " + ftpClient.getPort()
				+ "\r\n" + "Username: " + ftpClient.getUsername() + "\r\n"
				+ "Password: " + ftpClient.getPassword() + "\r\n"
				+ "isAuthenticated: " + ftpClient.isAuthenticated() + "\r\n"
				+ "isPassive: " + ftpClient.isPassive() + "\r\n" + "Type: "
				+ ftpClient.getType() + "\r\n" + "isResumeSupported: "
				+ ftpClient.isResumeSupported();
	}

	@Override
	public boolean downloadFile(String remotePath,	String localPath)
	{
		try {
			return download(remotePath, localPath);
		} catch (IOException e) {
			logger.error("download failed! [" +remotePath+"]\r\n"+e.getMessage());
			return false;
		}
	}

}
